package cn.b.sky.shop.storage.service.impl;

import cn.b.sky.shop.storage.CloudObjectMetadata;
import cn.b.sky.shop.storage.ProgressListener;
import cn.b.sky.shop.storage.TransferStrategy;
import cn.b.sky.shop.storage.exception.StorageException;
import cn.b.sky.shop.storage.utils.IOUtil;
import cn.b.sky.shop.storage.utils.ProgressAwareInputStream;
import cn.b.sky.shop.storage.utils.ProgressAwareOutputStream;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: lidapeng
 * @Description:
 * @Date Created in 8/23/23 10:21 AM
 * @Modified By: lidapeng
 * Copyright(c) dbappsecurity.com.cn
 */
@Component("minioClient")
@Slf4j
public class CloudMinioClient extends AbstractCloudStorageClient {

    private static final long MAX_OBJECT_SIZE = 5497558138880L;
    private static final String defaultContentType = "application/octet-stream";

    @Resource
    private MinioClient minioClient;

    public CloudMinioClient(String bucketName, List<TransferStrategy> transferStrategies) {
        super(bucketName, transferStrategies, null);
    }

    protected String preparePath(String path) {
        if (path.startsWith("/") || path.startsWith("\\")) {
            path = path.substring(1);
        }
        path = path.replaceFirst(":/", "/");
        path = path.replaceFirst(":\\\\", "/");
        return path.replaceAll("\\\\", "/");
    }

    /**
     * 存储对象
     *
     * @param bais
     * @param metadata
     * @param progressListener
     */
    @Override
    protected void putObject(String objectName, InputStream bais, CloudObjectMetadata metadata, ProgressListener progressListener) {
        try {
            Map<String, String> headers = new HashMap();
            if (metadata != null && metadata.getUserHeaders() != null) {
                headers.putAll(metadata.getUserHeaders());
            }

            long size = metadata != null ? metadata.getContentLength() : 5497558138880L;
            if (progressListener != null && size > 0L) {
                bais = new ProgressAwareInputStream((InputStream) bais, size, progressListener);
            }

            InputStream stream = this.prepareInputStream((InputStream) bais);
            this.minioClient.putObject(PutObjectArgs.builder().bucket(this.bucketName).object(objectName).stream(
                    bais, bais.available(), -1)
                    .build());
        } catch (Exception e) {
            throw this.wrapOssException(e, "putObject", objectName, bais, metadata, progressListener);
        }
    }

    /**
     * 删除桶
     *
     * @param bucket
     * @param region
     */
    @Override
    public void removeBucket(String bucket, String region) {

    }

    /**
     * 上次本地文件到storage
     *
     * @param object
     * @param localFile 本地文件
     * @return
     */
    @Override
    public CloudObjectMetadata transForLocalFile(String objectName, File localFile) {
        if (objectName.endsWith("/")) {
            throw new StorageException("文件不能以'/'结尾");
        }
        String cosPath = this.preparePath(objectName);
        Closeable closeable = null;
        CloudObjectMetadata metadata = new CloudObjectMetadata(cosPath, new Date(localFile.lastModified()), localFile.length());

        try {
            closeable = new FileInputStream(localFile);
        } catch (FileNotFoundException var12) {
            throw new StorageException("文件不存在：" + localFile.getAbsolutePath());
        }
        this.checkFileSize(metadata != null ? metadata.getContentLength() : null);
        this.putObject(objectName, this.prepareInputStream((InputStream) closeable), metadata, null);
        IOUtil.close(closeable);
        return metadata;
    }

    /**
     * 通过服务器端从另一个对象复制数据来创建一个对象
     *
     * @param srcObject
     * @param destObject
     * @param progressListener
     * @throws StorageException
     */
    @Override
    public void copy(String srcObject, String destBucket, String destObject, ProgressListener progressListener) throws StorageException {

        srcObject = this.preparePath(srcObject);
        destObject = this.preparePath(destObject);
        String destinationBucketName = destBucket != null ? destBucket : this.bucketName;
        if (srcObject.length() != 0 && destObject.length() != 0) {
            if (!srcObject.endsWith("/") && !destObject.endsWith("/")) {
                CloudObjectMetadata file = this.getObjectMeta(srcObject);
                if (file == null) {
                    throw new StorageException("源文件不存在");
                }
                try {
                    minioClient.copyObject(CopyObjectArgs.builder().bucket(destinationBucketName).object(destObject)
                            .source(CopySource.builder().bucket(this.bucketName)
                                    .object(srcObject).build()).build());
                } catch (Exception e) {
                    throw this.wrapOssException(e, "copyObject", srcObject, destObject);
                }
            }
        }
    }

    /**
     * 获取对象流
     *
     * @param object
     * @param listener
     * @return
     */
    @Override
    public InputStream getObject(String object) {
        try {
            return this.prepareInputStream(minioClient.getObject(GetObjectArgs.builder()
                    .bucket(this.bucketName).object(object).build()));
        } catch (Exception e) {
            if (e instanceof ErrorResponseException) {
                ErrorResponseException ere = (ErrorResponseException) e;
                if ("NoSuchKey".equals(ere.errorResponse().code())) {
                    return null;
                }
            }

            throw this.wrapOssException(e, "readAsStream", object);
        }
    }

    /**
     * 下载对象到本地文件
     *
     * @param object
     * @param filePath
     */
    @Override
    public void downloadObject(String object, String filePath, ProgressListener listener) {
        try {

            OutputStream os = null;
            InputStream stream = null;

            try {
                CloudObjectMetadata meta = this.getObjectMeta(object);
                stream = this.getObject(object);
                File localStorage = new File(filePath);
                if (localStorage.exists()) {
                    localStorage.delete();
                }
                if (listener != null) {
                    os = new ProgressAwareOutputStream(new FileOutputStream(localStorage), meta.getContentLength(), listener);
                } else {
                    os = new FileOutputStream(localStorage);
                }
                IOUtil.copy(stream, os);
                os.flush();
            } finally {
                IOUtil.closeQuietly(os);
                IOUtil.closeQuietly(stream);
            }
        } catch (Exception var12) {
            throw this.wrapOssException(var12, "getObject", object);
        }
    }

    /**
     * 获取对象的基本信息
     *
     * @param object
     * @return
     */
    @Override
    public CloudObjectMetadata getObjectMeta(String object) {
        try {
            StatObjectResponse objectStat = this.minioClient.statObject(StatObjectArgs.builder()
                    .bucket(this.bucketName)
                    .object(object)
                    .build());
            return this.fromObjectStat(objectStat);
        } catch (Exception var4) {
            if (var4 instanceof ErrorResponseException) {
                ErrorResponseException ere = (ErrorResponseException) var4;
                if ("NoSuchKey".equals(ere.errorResponse().code())) {
                    return null;
                }
            }

            throw this.wrapOssException(var4, "getObjectMeta", object);
        }
    }

    /**
     * 获取一个指定了 HTTP 方法、到期时间和自定义请求参数的对象URL地址，
     * 也就是返回带签名的URL，这个地址可以提供给没有登录的第三方共享访问或者上传对象。
     *
     * @param method        方法
     * @param object        对象
     * @param expireSeconds 过期时间
     * @return
     */
    @Override
    public String getPresignedObjectUrl(String method, String object, int expireSeconds) {
        try {
            String url =
                    minioClient.getPresignedObjectUrl(
                            GetPresignedObjectUrlArgs.builder()
                                    .method(Method.valueOf(method))
                                    .bucket(this.bucketName)
                                    .object(object)
                                    .expiry(expireSeconds)
                                    .build());
            return url;
        } catch (Exception e) {
            throw this.wrapOssException(e, "getPresignedObjectUrl", object, expireSeconds);
        }
    }

    /**
     * @param object
     * @param fileName
     * @param expireSeconds
     * @return URL
     */
    @Override
    public URL getPresignedObject(String object, String fileName, int expireSeconds) {

        try {
            Map<String, String> map = new HashMap();
            if (fileName != null) {
                try {
                    map.put("response-content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
                } catch (UnsupportedEncodingException var7) {
                    map.put("response-content-disposition", "attachment;filename=" + fileName);
                }
            } else {
                map.put("response-content-disposition", "attachment;");
            }
            String url =
                    minioClient.getPresignedObjectUrl(
                            GetPresignedObjectUrlArgs.builder()
                                    .method(Method.GET)
                                    .bucket(this.bucketName)
                                    .object(object)
                                    .expiry(expireSeconds)
                                    .extraQueryParams(map)
                                    .build());
            return new URL(url);
        } catch (Exception e) {
            throw this.wrapOssException(e, "getPresignedObjectUrl", object, expireSeconds);
        }

    }

    /**
     * 删除对象
     *
     * @param object
     */
    @Override
    public void removeObject(String object) {
        String cosPath = this.preparePath(object);
        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder().bucket(this.bucketName).object(object).build());
        } catch (Exception e) {
            throw this.wrapOssException(e, "removeObject", object);
        }
    }

    private StorageException wrapOssException(Throwable e, String operation, Object... params) {
        if (e instanceof StorageException) {
            return (StorageException) e;
        } else {
            boolean wrapped = false;
            String msg = e.getMessage();
            if (e instanceof ErrorResponseException) {
                ErrorResponseException ere = (ErrorResponseException) e;
                String code = ere.errorResponse().code();
                msg = ere.errorResponse().message();
            }
            if (!wrapped) {
                this.log.error("{}({}) fail,{}", new Object[]{operation, params == null ? "" : StringUtils.join(params, ","), e.toString(), e});
            }
            return new StorageException("minioClient", msg, e);
        }
    }

    private CloudObjectMetadata fromObjectStat(StatObjectResponse objectStat) {
        CloudObjectMetadata cloudObjectMetadata = new CloudObjectMetadata(objectStat.object(),
                Date.from(objectStat.lastModified().toInstant()), objectStat.size());
        if (objectStat.userMetadata() != null) {
            cloudObjectMetadata.getUserHeaders().putAll(objectStat.userMetadata());
        }
        Map<String, List<String>> httpHeaders = objectStat.headers().toMultimap();
        if (httpHeaders != null) {
            for (String key : httpHeaders.keySet()) {
                List<String> headerValue = httpHeaders.get(key);
                if (CollectionUtils.isEmpty(headerValue)) {
                    continue;
                }
                if (key.startsWith("x-amz-meta-")) {
                    cloudObjectMetadata.getUserHeaders().put(key, headerValue.get(0));
                } else {
                    cloudObjectMetadata.getHeaders().put(key, headerValue.get(0));
                }
            }
        }
        cloudObjectMetadata.setMd5sum(objectStat.etag());
        return cloudObjectMetadata;
    }
}
